package jpa.entities;

import java.io.Serializable;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

/**
 * @author Remco Haszing
 */
@Entity
@Table(name = "Employee")
@NamedQueries({
    @NamedQuery(name = "Employee.findByUsername", query = "SELECT a FROM Employee AS a WHERE a.userName = :username AND a.password = :password")
})
public class Employee implements Serializable {

    // properties
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @Column(unique = true)
    private String userName;
    private String lastName;
    private String firstName;
    private String middleNames;
    @Temporal(TemporalType.DATE)
    private GregorianCalendar birthDate;
    @Temporal(TemporalType.DATE)
    private GregorianCalendar membershipDate;
    @Temporal(TemporalType.DATE)
    private GregorianCalendar terminationDate;
    private String town;
    private String postalCode;
    private String country;
    private long socialSecurityNumber;
    @ElementCollection
    private List<String> phoneNumbers;
    @ElementCollection
    private List<String> emailAddresses;
    @ElementCollection
    private List<String> webAddresses;
    private String password;
    private boolean admin;
    @ManyToMany
    @JoinTable(name = "ProjectMembers",
    joinColumns = {
        @JoinColumn(name = "employee")},
    inverseJoinColumns = {
        @JoinColumn(name = "project")})
    private List<Project> projects;
    @OneToMany(cascade = CascadeType.PERSIST)
    private List<Task> tasks;

    // constructor
    /**
     * This constructor is used by the JPA API and should not be used for
     * anything else.
     *
     * @deprecated Use {@link #Employee(String, String, String, String,
     * GregorianCalendar, GregorianCalendar, GregorianCalendar, String, String,
     * String, long, ArrayList, ArrayList, ArrayList, String)} instead.
     */
    @Deprecated
    public Employee() {
    }

    /**
     * Creates a new employee. This constructor creates a new employee
     * containing all of the given properties. Phone numbers and email addresses
     * will only be added if the are formatted correctly.
     *
     * @param userName The username of the employee. This should be a unique
     * value.
     * @param lastName The last name of the employee.
     * @param firstName The first name of the employee.
     * @param middleNames The middle names of the employee as a single string.
     * @param birthDate The birth date of the employee.
     * @param membershipDate The membership date of the employee.
     * @param terminationDate The termination date of the employee.
     * @param town The town where this employee lives.
     * @param postalCode The postal code of the area where the employee lives.
     * @param country The country in which the employee lives.
     * @param socialSecurityNumber The social security number of the employee.
     * This should be a unique number.
     * @param phoneNumbers A list containing the employee's phone numbers.
     * @param emailAddresses A list containing the employee's email addresses.
     * @param webAddresses A list containing the employee's web addresses.
     * @param password The password the employee is able to log in with. The
     * password itself is never stored.
     * @param admin Wether or not this employee is an admin.
     */
    public Employee(String userName, String lastName, String firstName, String middleNames, GregorianCalendar birthDate, GregorianCalendar membershipDate, GregorianCalendar terminationDate, String town, String postalCode, String country, long socialSecurityNumber, String[] phoneNumbers, String[] emailAddresses, String[] webAddresses, String password, boolean admin) {
        this.userName = userName.toLowerCase();
        this.lastName = lastName;
        this.firstName = firstName;
        this.birthDate = birthDate;
        this.membershipDate = membershipDate;
        this.terminationDate = terminationDate;
        this.town = town;
        this.postalCode = postalCode;
        this.country = country;
        this.socialSecurityNumber = socialSecurityNumber;
        this.password = sha1(password);
        this.phoneNumbers = new ArrayList<String>();
        this.emailAddresses = new ArrayList<String>();
        this.webAddresses = new ArrayList<String>();
        this.tasks = new ArrayList<Task>();
        this.admin = admin;
        this.setPhoneNumbers(phoneNumbers);
        this.setEmailAddresses(emailAddresses);
        this.setWebAddresses(webAddresses);
    }

    // getters
    /**
     * @return This employee's unique id number.
     */
    public long getId() {
        return this.id;
    }

    /**
     * @return This employee's username.
     */
    public String getUserName() {
        return this.userName;
    }

    /**
     * @return This employee's last name.
     */
    public String getLastName() {
        return this.lastName;
    }

    /**
     * @return This employee's first name.
     */
    public String getFirstName() {
        return this.firstName;
    }

    /**
     * @return This employee's middle names in one string.
     */
    public String getMiddleNames() {
        return this.middleNames;
    }

    /**
     * @return This employee's birth date.
     */
    public GregorianCalendar getBirthDate() {
        return this.birthDate;
    }

    /**
     * @return This employee's membership date.
     */
    public GregorianCalendar getMembershipDate() {
        return this.membershipDate;
    }

    /**
     * @return This employee's termination date.
     */
    public GregorianCalendar getTerminationDate() {
        return this.terminationDate;
    }

    /**
     * @return The town where this employee lives.
     */
    public String getTown() {
        return this.town;
    }

    /**
     * @return The postal code of the employee's home address.
     */
    public String getPostalCode() {
        return this.postalCode;
    }

    /**
     * @return The country where this employee lives.
     */
    public String getCountry() {
        return this.country;
    }

    /**
     * @return This employee's social security number.
     */
    public long getSocialSecurityNumber() {
        return this.socialSecurityNumber;
    }

    /**
     * @return An array containing all projects this employee is working on.
     */
    public Project[] getProjects() {
        return this.projects.toArray(new Project[0]);
    }

    /**
     * @return An array containing all of this employee's phone numbers.
     */
    public String[] getPhoneNumbers() {
        return this.phoneNumbers.toArray(new String[0]);
    }

    /**
     * @return An array containing all of this employee's email addresses.
     */
    public String[] getEmailAddresses() {
        return this.emailAddresses.toArray(new String[0]);
    }

    /**
     * @return An array containing all of this employee's web addresses.
     */
    public String[] getWebAddresses() {
        return this.webAddresses.toArray(new String[0]);
    }

    /**
     * @return An array containing all tasks of this employee.
     */
    public Task[] getTasks() {
        return this.tasks.toArray(new Task[0]);
    }

    /**
     * @return Wether or not this employee is an admin.
     */
    public boolean isAdmin() {
        return this.admin;
    }

    // setters
    /**
     * Sets the last name of this employee. This method sets the last name of
     * this employee.
     *
     * @param lastName The new name of this employee.
     */
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    /**
     * Sets the username of this employee. This method sets the username of this
     * employee.
     *
     * @param username The new name of this employee.
     */
    public void setUserName(String userName) {
        this.userName = userName;
    }

    /**
     * Sets the membership date of this employee. This method sets the
     * membership date of this employee.
     *
     * @param membershipDate The membership date of this employee.
     */
    public void setMembershipDate(GregorianCalendar membershipDate) {
        this.membershipDate = membershipDate;
    }

    /**
     * Sets the termination date of this employee. This method sets the
     * termination date of this employee.
     *
     * @param terminationDate The terminationDate of this employee.
     */
    public void setTerminationDate(GregorianCalendar terminationDate) {
        this.terminationDate = terminationDate;
    }

    /**
     * Sets the town of this employee. This method sets the town of this
     * employee.
     *
     * @param town The new town of this employee.
     */
    public void setTown(String town) {
        this.town = town;
    }

    /**
     * Sets this Employee's password. This method sets the password of this
     * Employee.
     *
     * @param password The new password to be set.
     */
    public void setPassword(String password) {
        this.password = sha1(password);
    }

    /**
     * Sets the postal code of this employee. This method sets the postal code
     * of this employee.
     *
     * @param postalCode The new postal code of this employee.
     */
    public void setPostalCode(String postalCode) {
        this.postalCode = postalCode;
    }

    /**
     * Sets the country of this employee. This method sets the country of this
     * employee.
     *
     * @param country The new country of this employee.
     */
    public void setCountry(String country) {
        this.country = country;
    }

    /**
     * Sets the phone numbers of this employee. First this method clears all
     * phone numbers. Then it checks for all phone numbers if they are valid and
     * adds them if so.
     *
     * @param phoneNumbers The new phone numbers to be set.
     */
    public void setPhoneNumbers(String[] phoneNumbers) {
        this.phoneNumbers.clear();
        for (String p : phoneNumbers) {
            p.replaceAll("[+]", "00").replaceAll("[- ]+", "");
            if (p.matches("\\d{13}")) {
                this.phoneNumbers.add(p);
            }
        }
    }

    /**
     * Sets the email addresses of this employee. First this method clears all
     * web addresses. Then it checks for all email addresses if they are valid
     * and adds them if so.
     *
     * @param emailAddresses The new email addresses to be set.
     */
    public void setEmailAddresses(String[] emailAddresses) {
        this.emailAddresses.clear();
        for (String e : emailAddresses) {
            e.toLowerCase();

            Pattern p = Pattern.compile(".+@.+\\.[a-z]+");
            // Match the given string with the pattern
            Matcher m = p.matcher(e);
            // check whether match is found
            boolean matchFound = m.matches();

            if (matchFound) {
                this.emailAddresses.add(e);
            }
        }
    }

    /**
     * Sets the web addresses of this employee. First this method clears all web
     * addresses. Then it checks for all web addresses if they are valid and
     * adds them if so.
     *
     * @param webAddresses The new email addresses to be set.
     */
    public void setWebAddresses(String[] webAddresses) {
        this.webAddresses.clear();
        for (String w : webAddresses) {
            w.toLowerCase();

            Pattern p = Pattern.compile(".+\\.[a-z]+");
            // Match the given string with the pattern
            Matcher m = p.matcher(w);
            boolean matchFound = m.matches();

            if (matchFound) {
                this.webAddresses.add(w);
            }
        }
    }

    // methods
    /**
     * Calculates the sha1 hash value of a String. This method calculates the
     * sha1 hash of a string and returns a new string representing this hash.
     *
     * @param string The string that needs to be encrypted.
     * @return The encrypted string as a new string.
     */
    public static String sha1(String string) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            Formatter f = new Formatter();
            for (byte b : md.digest(string.getBytes())) {
                f.format("%02x", b);
            }
            return f.toString();
        } catch (NoSuchAlgorithmException ex) {
            return "";
        }
    }

    /**
     * Adds a project to this employee. This method adds a new project to this
     * employee's list of projects.
     *
     * @param project The project to be added
     */
    public void addProject(Project project) {
        if (!this.projects.contains(project)) {
            this.projects.add(project);
            project.addMember(this);
        }
    }
    
        /**
     * Removes a project from this employee. This method removes a project from this
     * employee's list of projects.
     *
     * @param project The project to be removed
     */
    public void removeProject(Project project) {
        if (this.projects.contains(project)) {
            this.projects.remove(project);
            project.removeMember(this);
        }
    }

    /**
     * Adds a task to this employee's task list. This method adds a task to the
     * list of tasks this employee has to do.
     *
     * @param task The new task to be added.
     */
    public void addTask(Task task) {
        this.tasks.add(task);
    }

    /**
     * Validates wether the given value matches this employee's password. This
     * method validates wether this the given value matches this employee's
     * password or not. It returns true if they do match, otherwise it returns
     * false.
     *
     * @param password The password to compare to.
     * @return Wether or not the given value matches this employee's password.
     */
    public boolean validatePassword(String password) {
        return sha1(password).equals(this.password);
    }
}
